import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.StringTokenizer;

/**
 * Ez az objektum tartalmazza a játék belépési pontját, az állapotváltozóit,
 * ez tölti be a pályákat, lép át a következő pályára, kezeli az életek számát.
 * Továbbá fogadja a mozgás illetve bombalerakás parancsokat.
 */
public class Game
{
  //A mezőket tároló lista
  private ArrayList _fields = new ArrayList();
  //Időzítő
  private Timer _timer;
  //A pálya oszlopainak száma
  private int column = 1;
  //A jtáékososk listája
  //0 -> JuzKez
  //1 -> JuzLab
  private Player[] _players = new Player[2];

  //Az utoljára mozgatott játékos indexe
  private int lastStepPlayer = -1;
  //Pályán lévő játékosok száma
  private int playersLeft = 0;

  //Be van e töltve a pálya
  private boolean mapLoaded = false;

  //Hátralevő gyémántok száma
  private int diamonds = 0;
  //Van-e a pályán felvehető uránium
  private boolean uraniumIsOnField;
  //A bombák robbanóereje
  private int bombPower = 1;

  private static boolean printMap = false;

  //A bombák késleltetése
  private int bombTimer = 3;
  //EBbe kerül a futtatható állomány elérési útja
  private String mapsDir;
  //Maximális tolási mélység
  private int maxDepth = 2;

  /**
   * A program belépési pontja.
   * @param args
   */
  public static void main(String[] args)
  {
    //Debug célra, ha -p paraméterel indul a program, minden lépés után
    //megjelenítjük az aktuális pálya tartalmat.
    if(args.length > 0 && args[0].equals("-p"))
    {
      printMap = true;
    }
    Game game = new Game();
    game.init();
  }

  /**
   * Itt inicializálódik a játék.
   */
  public void init()
  {
    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    String input;
    mapLoaded = false;

    //A futtatható állomány elérési útjának lekérése.
    File directory = new File (".");
    try
    {
      mapsDir = directory.getCanonicalPath();
    }
    catch (IOException ex)
    {
      System.exit(-2);
    }
    if(mapsDir.charAt(mapsDir.length()-1) != '\\')
      mapsDir += "\\";

    _timer = new Timer();

    try 
    {
      //MainLoop, mindig beolvassuk a következő parancsot
      while ((input = in.readLine()) != null && input.length() != 0)
      {
        //Parancs továbbítás
        dispatchMessage(input);
      }
    }
    catch (IOException ex)
    {
      System.exit(-1);
    }
  }

  /**
   * Ideiglenes függvény, nem része a játéknak, csupán debug célokat szolgál.
   * Kiírja az éppen aktuális pálya tartalmat, a páylaleyró fájlok formátumában.
   */
  private void printStuff()
  {
    int j = 1;
    for(int i = 0;i<_fields.size();i++)
    {
      if(((Field)_fields.get(i)).getEntity() instanceof Player )
      {
        if(((Field)_fields.get(i)).getEntity() == _players[0])
          System.out.print("P");
        else
          System.out.print("p");
      }

      else if(((Field)_fields.get(i)).getEntity() instanceof Wall )
      {
        System.out.print("w");
      }

      else if(((Field)_fields.get(i)).getEntity() instanceof Diamond )
      {
        System.out.print("d");
      }

      else if(((Field)_fields.get(i)).getEntity() instanceof Sandstone )
      {
        System.out.print("s");
      }

      else if(((Field)_fields.get(i)).getEntity() instanceof Granite )
      {
        System.out.print("g");
      }

      else if(((Field)_fields.get(i)).getEntity() instanceof Bomb )
      {
        System.out.print("B");
      }

      else if(((Field)_fields.get(i)).getEntity() instanceof Exit )
      {
        System.out.print("e");
      }

      else if(((Field)_fields.get(i)).getEntity() instanceof Multiplier )
      {
        System.out.print("M");
      }

      else if(((Field)_fields.get(i)).getEntity() instanceof Uranium )
      {
        System.out.print("u");
      }

      else if(((Field)_fields.get(i)).getEntity() instanceof Tracker )
      {
        System.out.print("T");
      }

      else if(((Field)_fields.get(i)).getEntity() instanceof Boulder )
      {
        System.out.print("b");
      }

      else
        System.out.print(" ");

      if(j == column)
      {
        System.out.print("\r\n");
        j = 1;
      }
      else
        j++;
    }
    System.out.print("\r\n");
  }

  /**
   * A parancs továbbító függvény. Kinyeri a beérkező parancsokat és a hozzájuk
   * tartozó paramétereket, majd végrehajtja a nekik megfelelő műveletet
   * @param message A beérkező parancs.
   */
  private void dispatchMessage(String message)
  {
    StringTokenizer tokenizer = new StringTokenizer(message);

    //Parancs kinyerése
    String command = tokenizer.nextToken();

    if(command.equals("load"))
    {
      if(!loadMap(tokenizer.nextToken()))
      {
        System.out.println("map not loaded");
      }

      else
      {
        mapLoaded = true;
      }

      return;
    }

    else if(command.equals("end"))
    {
      System.exit(0);
    }

    if(mapLoaded)
    {
      if(command.equals("moveJK"))
      {
        step(Integer.valueOf(tokenizer.nextToken()), 0);
        if(printMap)
          printStuff();
        return;
      }

      else if(command.equals("moveJL"))
      {
        step(Integer.valueOf(tokenizer.nextToken()), 1);
        if(printMap)
          printStuff();
        return;
      }

      else if(command.equals("tick"))
      {
        _timer.tick();
        if(printMap)
          printStuff();
        return;
      }

      else if(command.equals("putbombJK"))
      {
        setBomb(0);
        if(printMap)
          printStuff();
        return;
      }

      else if(command.equals("putbombJL"))
      {
        setBomb(1);
        if(printMap)
          printStuff();
        return;
      }

      else if(command.equals("startmap"))
      {
        startBoard();
        if(printMap)
          printStuff();
        return;
      }
    }
    return;
  }

  /**
   * Meghíváskor az utoljára lépett játékos elhagyja a pályát.
   */
  public void exitLastPlayer()
  {
    _players[lastStepPlayer].field.delete();
    if(playersLeft > 0)
      playersLeft--;
    else
      nextLevel();
  }

  /**
   * Beállítja egy mező szomszédját egy másikra, a megadott irányba.
   * @param field1 A beállítandó mező referenciája.
   * @param direction Az irány.
   * @param field2 A szomszédnak beállytandó mező referenciája.
   */
  private void setNeighbour(int field1, int direction, int field2)
  {
    try
    {
      if (((direction == 3 || direction == 4) && ((field2 % column ) != 0)) ||
          ((direction == 1 || direction == 6) && ((field1 % column) != 0)) ||
          (direction == 2) ||
          (direction == 5))
        ((Field)_fields.get(field1)).setNeighbour(direction, ((Field)_fields.get(field2)));
    }
    catch (Exception e)
    {
      //ezt itt szépen benyeljük...
    }
  }
  


  /**
   * Feltölt egy szintet a megadott páylaleíró alapján.
   * @param filename A betöltendő pályát tartalmazó file elérési útja.
   */
  private boolean loadMap(String filename)
  {      
    BufferedReader in = null;

    //AZ oszlopok számát nyilvántartó változó
    column = 1;

    //Az aktuális sort nyilvántartó változó
    int cLine = 1;

    Field field;
    Entity entity;

    //Alapvető pályaváltozók nullázása.
    _fields.clear();
    _players[0] = null;
    _players[1] = null;
    diamonds = 0;

    try
    {
      File file = new File(mapsDir + "maps\\" + filename);
      if(!file.exists())
      {
        System.out.println("File does not exist.");
        return false;
      }
      in = new BufferedReader(new FileReader(file));
      while(in.ready())
      {
        //A beolvasott sor felsolgozása
        String currentLine = in.readLine();
        column = currentLine.length();

        //A sorban szereplő mezőket létrehozó ciklus
        for(int i = 0; i< currentLine.length() ;i++)
        {
          SkeletonHelper.setLastPoint(cLine, i+1);
          field = new Field(this);
          switch(currentLine.charAt(i))
          {
            case 'w':
              entity = new Wall();
              break;
            case 'e':
              entity = new Exit();
              break;
            case 'p':
              entity = new Player();
              _players[1] = (Player)entity;
              playersLeft++;
              break;
            case 'P':
              entity = new Player();
              _players[0] = (Player)entity;
              playersLeft++;
              break;
            case 's':
              entity = new Sandstone();
              break;
            case 'd':
              entity = new Diamond();
              diamonds++;
              break;
            case 'g':
              entity = new Granite();
              break;
            case 'u':
              entity = new Uranium();
              setUranium();
              break;
            case 'M':
              entity = new Multiplier();
              _timer.add((ITickable)entity);
              break;
            case 'b':
              entity = new Boulder();
              break;
            case 'T':
              entity = new Tracker();
              _timer.add((ITickable)entity);
              break;
            case 'B':
              entity = new Bomb(bombPower, bombTimer);
              _timer.add((ITickable)entity);
              break;
            case 'G':
              entity = new Gemster();
              _timer.add((ITickable)entity);
              break;
            default:
              entity = null;
              break;
          }
          if(entity != null)
          {
            entity.setField(field);
            field.add(entity);
          }
          _fields.add(field);
        }

        cLine++;
      }
    }
    catch (Exception e)
    {
      return false;
    }
    finally
    {
      try
      {
        in.close();
      }
      catch(Exception e)
      {
        return false;
      }
    }

    SkeletonHelper.setLastPoint(0, 0);

    //linkek létrehozása
    for (int i = 0; i < _fields.size(); i++)  //i az aktuális mező indexe
    {
      //lefelé
      setNeighbour(i, 5, i + column);
      //felfelé
      setNeighbour(i, 2, i - column);
      //balra fel
      setNeighbour(i, 1, i - 1 - (((i%column)+1)%2)*column);
      //jobbra le
      setNeighbour(i, 4, i + 1 + ((i%column)%2)*column);
      //balra le
      setNeighbour(i, 6, i - 1 + ((i%column)%2)*column);
      //jobbra fel
      setNeighbour(i, 3, i + 1 - (((i%column)+1)%2)*column);
    }
    
    /*for(int i = 0; i < _fields.size();i++)
    {
      System.out.println("Field: " + ((Field)_fields.get(i)).ID);
      for(int j = 1 ;j < 7;j++ )
      {
        try
        {
          System.out.println("  " + j + ": " + ((Field)_fields.get(i)).getNeighbour(j).ID);
        }
        catch(Exception e)
        {}
      }
    }*/

    System.out.println("map " + filename + " loaded");

    return true;
  }

  /**
   * Lépteti a játékost egy adott irányba. Illetve megjegyzi az utoljára mozgó
   * játékost.
   * @param direction A lépés iránya.
   * @param player A léptetendő játékos.
   */
  public void step(int direction, int player)
  {
    _players[player].move(direction, maxDepth);
    lastStepPlayer = player;
  }

  /**
   * Alaphelyzetbe állítja a pályát.
   */
  public void reset()
  {

  }

  /**
   * Szünetelteti a játékot.
   * Protoban nincs feladata.
   */
  public void pause()
  {

  }

  /**
   * Csak a pálya elindítása lesz a feladata.
   * Jelen esetben a timert engedélyezi.
   */
  public void startBoard()
  {
    _timer.start();
  }

  /**
   * A proto-ban nincs szerepe.
   * A következő pályára lép.
   * @return Igaz, ha volt pálya még.
   */
  public boolean nextLevel()
  {
    return false;
  }

  /**
   * Eggyel növeli a felszedett gyémántok számát.
   */
  public void addDiamond()
  {
    if(diamonds > 0)
      diamonds--;
  }

  /**
   * Megmondja, hogyminden gyémánt fel lett-e szedve, elhagyható-e a pálya.
   * @return True, ha elhagyható, False, ha nem
   */
  public boolean canLeaveMap()
  {
    return diamonds == 0;
  }

  /**
   * Valamelyik játékos meghal
   * A prot-ban ez a szimuláció végét jelenti, kilépünk a programból
   */
  public void killPlayer()
  {
      System.exit(0);
  }

  /**
   * Letetet egy bombát a kiválasztott játékossal.
   * @param player A játékos amellyel bombát akarunk lerakni.
   */
  public void setBomb(int player)
  {
    _players[player].setBomb();
  }

  /**
   * Létrehozza a Bomb osztály egy példányát, majd visszaadja a hívónak.
   * @return
   */
  public Bomb createBomb()
  {
    Bomb b = new Bomb(bombPower, bombTimer);
    return b;
  }

  /**
   * Létrehozza a Diamond osztály egy példányát, majd visszaadja a hívónak.
   * @return
   */
  public Diamond createDiamond()
  {
    Diamond d = new Diamond();
    return d;
  }

  /**
   * Megnöveli a bombák hatósugarát.
   */
  public void increaseBombStrength()
  {
    bombPower++;
  }

  /**
   * Lerak egy darab uránt a pályára, egy szaporodó szörny halála esetén.
   */
  public void setUranium()
  {
    uraniumIsOnField = true;
  }

  /**
   * Visszaadja, hogy van-e a pályán urán.
   * @return
   */
  public boolean getUranium()
  {
    return uraniumIsOnField;
  }

  /**
   * Visszaadja az egyik playert reprezentáló referenciát.
   * @return A kért player.
   */
  public Player getPlayer(int param)
  {
    return _players[param];
  }

  /**
   * Visszaadja az időzítőt.
   * @return A Timerre mutató referencia.
   */
  public Timer getTimer()
  {
    return _timer;
  }
}